package com.hwn.datax.controller;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.hwn.datax.component.SpringFXMLLoader;
import com.hwn.datax.domain.*;
import com.hwn.datax.domain.datax.Reader;
import com.hwn.datax.domain.datax.Writer;
import com.hwn.datax.event.CustomSpringEvent;
import com.hwn.datax.service.*;
import com.hwn.datax.utils.AlertUtil;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Controller;

import java.util.*;

/**
 * 任务编辑
 *
 * @author wnhuang
 * @date 2024/5/31 17:56
 */
@Controller
public class JobEditController {

    @FXML
    public ProgressIndicator sourceDatabaseLoadingIndicator;

    @FXML
    public ProgressIndicator sourceTableLoadingIndicator;

    @FXML
    public ProgressIndicator targetDatabaseLoadingIndicator;
    @FXML
    public CheckBox truncateTable;
    @FXML
    public ComboBox<String> sourceTableComboBox;
    @FXML
    public ComboBox<String> targetTableComboBox;
    @FXML
    public VBox oneStep;
    @FXML
    public HBox twoStep;
    @FXML
    public Button lastStepBtn;
    @FXML
    public Button nextStepBtn;
    public RadioButton tableRadio;
    public RadioButton queryRadio;
    public TextField splitPkTextField;
    public TextField jobNameTextField;
    public TextField jvmParamTextField;
    public TextField fetchSizeTextField;
    public TextArea whereTextField;
    public TextField batchSizeTextField;
    public TextArea preSqlTextArea;
    public TextArea postSqlTextArea;
    public TextField speedRecordTextField;
    public TextField speedByteTextField;
    public TextField errorLimitRecordTextField;
    public TextField errorLimitPercentageTextField;
    public HBox threeStep;
    public ListView<String> sourceCoulmnNotMatchListView;
    public ListView<String> targetCoulmnNotMatchListView;
    public ListView<String> matchListView;
    public TextArea JobParamsTextArea;
    public HBox fourStep;
    public Button saveBtn;
    public Label titleLabel;

    @FXML
    private ComboBox<TreeItemNode<Long>> dataxGroupComboBox;

    @FXML
    private ComboBox<TreeItemNode<Integer>> dataxServerComboBox;

    @FXML
    private TextField channelTextField;

    @FXML
    private ComboBox<TreeItemNode<Long>> sourceDataSourceComboBox;

    @FXML
    private ComboBox<String> sourceDatabaseComboBox;

    @FXML
    private ComboBox<TreeItemNode<Long>> targetDataSourceComboBox;

    @FXML
    private ComboBox<String> targetDatabaseComboBox;

    @Autowired
    RepositorySourceService repositorySourceService;

    @Autowired
    DataxJobService dataxJobService;

    List<RepositorySource> repositorySourceList;

    @Autowired
    ConfigService configService;

    @Autowired
    ServerInfoService serverInfoService;

    @Autowired
    private DataxJobGroupService dataxJobGroupService;

    @Autowired
    SpringFXMLLoader springFXMLLoader;

    private int step = 1;

    private DataxJob dataxJob;

    List<String> titleList = new ArrayList<>(4);

    {
        titleList.add("基本信息");
        titleList.add("输入输出配置");
        titleList.add("字段匹配");
        titleList.add("参数预览");
    }


    public void setDataxJob(DataxJob dataxJob) {
        this.dataxJob = new DataxJob();
        BeanUtil.copyProperties(dataxJob, this.dataxJob);
        step = 1;
        setStepShow();
        updateUiFromModel();
    }


    private void updateModelFromUi() {
        dataxJob.setJobName(jobNameTextField.getText());

        DataxJob.Setting setting = dataxJob.getSetting();
        if (setting == null) {
            setting = new DataxJob.Setting();
            dataxJob.setSetting(setting);
        }

        Map<String, Object> speed = setting.getSpeed();
        if (speed == null) {
            speed = new HashMap<>();
            setting.setSpeed(speed);
        }
        speed.put("record", Convert.toInt(speedRecordTextField.getText(), 0));
        speed.put("byte", Convert.toInt(speedByteTextField.getText(), 0));
        speed.put("channel", Convert.toInt(channelTextField.getText(), 0));

        DataxJob.ErrorLimit errorLimit = setting.getErrorLimit();
        if (errorLimit == null) {
            errorLimit = new DataxJob.ErrorLimit();
            setting.setErrorLimit(errorLimit);
        }
        errorLimit.setRecord(Convert.toInt(errorLimitRecordTextField.getText(), 0));
        errorLimit.setPercentage(Convert.toDouble(errorLimitPercentageTextField.getText(), 0.0));

        TreeItemNode<Long> selectedGroupItem = dataxGroupComboBox.getSelectionModel().getSelectedItem();
        if (selectedGroupItem != null) {
            dataxJob.setGroupId(selectedGroupItem.getValue());
        }

        TreeItemNode<Integer> selectedServerItem = dataxServerComboBox.getSelectionModel().getSelectedItem();
        if (selectedServerItem != null) {
            dataxJob.setServerId(selectedServerItem.getValue());
        }

        Reader reader = dataxJob.getReader();
        if (reader == null) {
            reader = new Reader();
            dataxJob.setReader(reader);
        }
        TreeItemNode<Long> selectedSourceDataSource = sourceDataSourceComboBox.getSelectionModel().getSelectedItem();
        if (selectedSourceDataSource != null) {
            reader.setRepoId(selectedSourceDataSource.getValue());
        }
        reader.setSchemaName(sourceDatabaseComboBox.getSelectionModel().getSelectedItem());
        reader.setTableName(sourceTableComboBox.getSelectionModel().getSelectedItem());
        reader.setType(tableRadio.isSelected() ? 0 : 1);
        reader.setSplitPk(splitPkTextField.getText());
        reader.setFetchSize(Convert.toInt(fetchSizeTextField.getText(), 0));
        reader.setWhere(whereTextField.getText());

        Writer writer = dataxJob.getWriter();
        if (writer == null) {
            writer = new Writer();
            dataxJob.setWriter(writer);
        }
        TreeItemNode<Long> selectedTargetDataSource = targetDataSourceComboBox.getSelectionModel().getSelectedItem();
        if (selectedTargetDataSource != null) {
            writer.setRepoId(selectedTargetDataSource.getValue());
        }
        writer.setSchemaName(targetDatabaseComboBox.getSelectionModel().getSelectedItem());
        writer.setTableName(targetTableComboBox.getSelectionModel().getSelectedItem());
        writer.setBatchSize(Convert.toInt(batchSizeTextField.getText(), 0));
        writer.setPreSql(preSqlTextArea.getText());
        writer.setPostSql(postSqlTextArea.getText());
        writer.setTruncateTable(truncateTable.isSelected());

        dataxJob.setJvmParams(jvmParamTextField.getText());
    }


    private void updateUiFromModel() {
        jobNameTextField.setText(dataxJob.getJobName());
        DataxJob.Setting setting = dataxJob.getSetting();
        Map<String, Object> speed = setting.getSpeed();
        DataxJob.ErrorLimit errorLimit = setting.getErrorLimit();
        errorLimitRecordTextField.setText(Convert.toStr(errorLimit.getRecord()));
        errorLimitPercentageTextField.setText(Convert.toStr(errorLimit.getPercentage()));
        speedRecordTextField.setText(Convert.toStr(speed.get("record")));
        speedByteTextField.setText(Convert.toStr(speed.get("byte")));
        channelTextField.setText(Convert.toStr(speed.get("channel")));
        ObservableList<TreeItemNode<Long>> items = dataxGroupComboBox.getItems();
        if (Objects.nonNull(dataxJob.getGroupId())) {
            for (TreeItemNode<Long> treeItemNode : items) {
                if (treeItemNode.getValue().equals(dataxJob.getGroupId())) {
                    dataxGroupComboBox.getSelectionModel().select(treeItemNode);
                    break;
                }
            }
        }

        ObservableList<TreeItemNode<Integer>> items1 = dataxServerComboBox.getItems();
        if (Objects.nonNull(dataxJob.getServerId())) {
            for (TreeItemNode<Integer> treeItemNode : items1) {
                if (treeItemNode.getValue().equals(dataxJob.getServerId())) {
                    dataxServerComboBox.getSelectionModel().select(treeItemNode);
                    break;
                }
            }
        }

        Reader reader = dataxJob.getReader();
        if (Objects.nonNull(reader)) {
            if (reader.getRepoId() != null) {
                for (TreeItemNode<Long> treeItemNode : sourceDataSourceComboBox.getItems()) {
                    if (treeItemNode.getValue().equals(reader.getRepoId())) {
                        sourceDataSourceComboBox.getSelectionModel().select(treeItemNode);
                        break;
                    }
                }
            }
            if (reader.getSchemaName() != null) {
                sourceDatabaseComboBox.getSelectionModel().select(reader.getSchemaName());
            }
            if (reader.getTableName() != null) {
                sourceTableComboBox.getSelectionModel().select(reader.getTableName());
            }
            if (reader.getType().equals(0)) {
                tableRadio.setSelected(true);
            } else {
                queryRadio.setSelected(true);
            }
            splitPkTextField.setText(reader.getSplitPk());
            fetchSizeTextField.setText(Convert.toStr(reader.getFetchSize(), ""));
            whereTextField.setText(reader.getWhere());
        }
        Writer writer = dataxJob.getWriter();
        if (Objects.nonNull(writer)) {
            if (writer.getRepoId() != null) {
                for (TreeItemNode<Long> treeItemNode : targetDataSourceComboBox.getItems()) {
                    if (treeItemNode.getValue().equals(writer.getRepoId())) {
                        targetDataSourceComboBox.getSelectionModel().select(treeItemNode);
                        break;
                    }
                }
            }
            if (writer.getSchemaName() != null) {
                targetDatabaseComboBox.getSelectionModel().select(writer.getSchemaName());
            }
            if (writer.getTableName() != null) {
                targetTableComboBox.getSelectionModel().select(writer.getTableName());
            }
            batchSizeTextField.setText(Convert.toStr(writer.getBatchSize(), ""));
            preSqlTextArea.setText(writer.getPreSql());
            postSqlTextArea.setText(writer.getPostSql());
            truncateTable.setSelected(writer.getTruncateTable());
        }

        jvmParamTextField.setText(dataxJob.getJvmParams());
    }

    @FXML
    public void initialize() {

        lastStepBtn.setOnAction(e -> setCurrStep(-1));
        nextStepBtn.setOnAction(e -> setCurrStep(1));
        saveBtn.setOnAction(e -> {
            handleSave();
        });
        ToggleGroup osGroup = new ToggleGroup();
        tableRadio.setToggleGroup(osGroup);
        queryRadio.setToggleGroup(osGroup);

        List<DataxJobGroup> list = dataxJobGroupService.list();

        dataxGroupComboBox.getItems().clear();
        dataxGroupComboBox.getItems().addAll(list.stream().map(t -> new TreeItemNode<>(t.getGroupName(), t.getId())).toList());

        List<ServerInfo> serverInfoList = serverInfoService.list();
        dataxServerComboBox.getItems().clear();
        dataxServerComboBox.getItems().addAll(serverInfoList.stream().map(t -> new TreeItemNode<Integer>(t.getServerName(), t.getId())).toList());

        repositorySourceList = repositorySourceService.list();
        List<TreeItemNode<Long>> collect = repositorySourceList.stream().map(t -> new TreeItemNode<Long>(t.getName(), t.getId())).toList();
        ObservableList<TreeItemNode<Long>> treeItemNodes = FXCollections.observableArrayList(collect);
        sourceDataSourceComboBox.getItems().addAll(collect);
        targetDataSourceComboBox.getItems().addAll(collect);

        onDataSourceComboBoxChange(sourceDataSourceComboBox, sourceDatabaseComboBox, sourceTableComboBox, sourceDatabaseLoadingIndicator, sourceTableLoadingIndicator, true);
        onDataSourceComboBoxChange(targetDataSourceComboBox, targetDatabaseComboBox, targetTableComboBox, targetDatabaseLoadingIndicator, targetDatabaseLoadingIndicator, false);
    }

    private void setCurrStep(int addStep) {
        this.step = this.step + addStep;
        titleLabel.setText(titleList.get(step - 1));
        setStepShow();
        if (step == 3) {
            mapperColumn();
        }
        if (step == 4) {
            updateModelFromUi();
            dataxJob.setJobParams(JSONUtil.toJsonStr(buildJson(dataxJob), 4));
            JobParamsTextArea.setText(dataxJob.getJobParams());
        }
    }

    private void setStepShow() {
        oneStep.setVisible(step == 1);
        twoStep.setVisible(step == 2);
        threeStep.setVisible(step == 3);
        fourStep.setVisible(step == 4);
        lastStepBtn.setDisable(!(step > 1));
        nextStepBtn.setDisable(!(step < 4));
        saveBtn.setDisable(!(step == 4));
    }

    private void mapperColumn() {
        List<String> leftColumns = dataxJob.getReader().getAllColumns();
        List<String> rightColumns = dataxJob.getWriter().getAllColumns();
        Map<String, String> match = match(leftColumns, rightColumns);
        matchListView.getItems().clear();
        matchListView.getItems().addAll(match.entrySet().stream().map(t -> t.getKey() + "<-->" + t.getValue()).toList());
        sourceCoulmnNotMatchListView.getItems().clear();
        sourceCoulmnNotMatchListView.getItems().addAll(leftColumns.stream().filter(t -> !match.containsKey(t)).toList());
        targetCoulmnNotMatchListView.getItems().clear();
        targetCoulmnNotMatchListView.getItems().addAll(rightColumns.stream().filter(t -> !match.containsValue(t)).toList());
        dataxJob.getReader().setColumns(match.keySet().stream().toList());
        dataxJob.getWriter().setColumns(match.values().stream().toList());
    }

    private JSONObject buildJson(DataxJob job) {
        List<RepositorySource> repoList = repositorySourceList;
        JSONObject dataxPlugin = configService.getConfig("DataxPlugin");

        RepositorySource sourceRepository = repoList.stream().filter(t -> t.getId().equals(job.getReader().getRepoId())).findFirst().get();
        RepositorySource targetRepository = repoList.stream().filter(t -> t.getId().equals(job.getWriter().getRepoId())).findFirst().get();

        JSONObject dataxJob = new JSONObject();
        dataxJob.set("setting", job.getSetting());

        JSONObject reader = new JSONObject();
        reader.set("name", dataxPlugin.getJSONObject(sourceRepository.getDbTypeName()).getStr("reader"));

        JSONObject readerParameter = new JSONObject();
        readerParameter.set("username", sourceRepository.getUsername());
        readerParameter.set("password", sourceRepository.getPassword());
        readerParameter.set("connection", Collections.singletonList(new JSONObject().set("jdbcUrl", Collections.singletonList(sourceRepository.getUrl()))));

        if (job.getReader().getType() == 1) {
            readerParameter.getJSONArray("connection").getJSONObject(0).set("querySql", Collections.singletonList(job.getReader().getQuerySql()));
        } else {
            readerParameter.getJSONArray("connection").getJSONObject(0).set("table", List.of(job.getReader().getSchemaName() + "." + job.getReader().getTableName()));
            readerParameter.set("splitPk", job.getReader().getSplitPk());
            readerParameter.set("column", job.getReader().getColumns());
        }

        reader.set("parameter", readerParameter);

        JSONObject writer = new JSONObject();
        writer.set("name", dataxPlugin.getJSONObject(targetRepository.getDbTypeName()).getStr("writer"));

        JSONObject writerParameter = new JSONObject();
        writerParameter.set("username", targetRepository.getUsername());
        writerParameter.set("password", targetRepository.getPassword());
        writerParameter.set("batchSize", job.getWriter().getBatchSize());
        writerParameter.set("connection", Collections.singletonList(new JSONObject().set("jdbcUrl", targetRepository.getUrl()).set("table", List.of(job.getWriter().getSchemaName() + "." + job.getWriter().getTableName()))));
        writerParameter.set("column", job.getWriter().getColumns());

        List<String> preSqlList = new ArrayList<>(Arrays.asList(job.getWriter().getPreSql().split(";")));
        if (truncateTable.isSelected()) {
            preSqlList.add("truncate table " + job.getWriter().getSchemaName() + "." + job.getWriter().getTableName());
        }
        writerParameter.set("preSql", preSqlList);

        writerParameter.set("postSql", Arrays.asList(job.getWriter().getPostSql().split(";")));

        writer.set("parameter", writerParameter);

        List<JSONObject> content = new ArrayList<>();
        content.add(new JSONObject().set("reader", reader).set("writer", writer));

        dataxJob.set("content", content);

        return new JSONObject().set("job", dataxJob);
    }

    public static Map<String, String> match(List<String> arr1, List<String> arr2) {
        Map<String, String> result = new HashMap<>();
        for (String item1 : arr1) {
            String left = item1;
            if (item1.indexOf('.') > -1) {
                left = item1.substring(item1.indexOf('.') + 1);
            }

            for (String item2 : arr2) {
                String right = item2;
                if (item2.indexOf('.') > -1) {
                    right = item2.substring(item2.indexOf('.') + 1);
                }

                if (left.equalsIgnoreCase(right)) {
                    result.put(item1, item2);
//                    iter1.remove();
//                    iter2.remove();
                    break;
                }
            }
        }
        return result;
    }


    private void onDataSourceComboBoxChange
            (ComboBox<TreeItemNode<Long>> dataSourceComboBox, ComboBox<String> databaseComboBox, ComboBox<String> tableComboBox, ProgressIndicator
                    databaseLoadingIndicator, ProgressIndicator tableLoadingIndicator, Boolean isReader) {
        dataSourceComboBox.setOnAction(event -> {
            databaseLoadingIndicator.setVisible(true);
            Long repoId = dataSourceComboBox.getValue().getValue();
            Task<ObservableList<String>> task = getObservableSchemaListTask(repoId);

            task.setOnSucceeded(event1 -> {
                databaseComboBox.getItems().clear();
                databaseComboBox.setValue(null);
                tableComboBox.setValue(null);
                tableComboBox.getItems().clear();
                ObservableList<String> value = task.getValue();
                databaseComboBox.getItems().addAll(value);
                databaseLoadingIndicator.setVisible(false);
            });
            task.setOnFailed(event1 -> {
                // Handle any errors during data loading
                databaseLoadingIndicator.setVisible(false);
                Alert alert = new Alert(Alert.AlertType.ERROR, "获取数据库失败！" + event1.getSource().getException().getMessage());
                alert.showAndWait();
            });
            // Start the task in a new thread
            new Thread(task).start();
        });

        databaseComboBox.setOnAction(event -> {
            if (databaseComboBox.getValue() == null) {
                return;
            }
            Long repoId = dataSourceComboBox.getValue().getValue();
            Task<ObservableList<String>> task = getObservableTableListTask(repoId, databaseComboBox.getValue());
            task.setOnSucceeded(event1 -> {
                tableComboBox.setValue(null);
                tableComboBox.getItems().clear();
                tableComboBox.getItems().addAll(task.getValue());
                tableLoadingIndicator.setVisible(false);
            });
            task.setOnFailed(event1 -> {
                // Handle any errors during data loading
                tableLoadingIndicator.setVisible(false);
                Alert alert = new Alert(Alert.AlertType.ERROR, "获取表数据失败！" + event1.getSource().getException().getMessage());
                alert.showAndWait();
            });
            tableLoadingIndicator.setVisible(true);
            new Thread(task).start();
        });

        tableComboBox.setOnAction(event -> {
            if (tableComboBox.getValue() == null) {
                return;
            }
            Long repoId = dataSourceComboBox.getValue().getValue();
            String database = databaseComboBox.getValue();
            String table = tableComboBox.getValue();
            Task<ObservableList<String>> task = getObservableColumnListTask(repoId, database, table, isReader);
            task.setOnSucceeded(event1 -> {
                tableLoadingIndicator.setVisible(false);
                if (isReader) {
                    dataxJob.getReader().setAllColumns(task.getValue());
                } else {
                    dataxJob.getWriter().setAllColumns(task.getValue());
                }
            });
            task.setOnFailed(event1 -> {
                tableLoadingIndicator.setVisible(false);
            });
            tableLoadingIndicator.setVisible(true);
            new Thread(task).start();
        });
    }


    private Task<ObservableList<String>> getObservableSchemaListTask(Long repoId) {
        return new Task<>() {
            @Override
            protected ObservableList<String> call() throws Exception {
                List<LinkedHashMap<String, Object>> schemas = repositorySourceService.getSchemas(String.valueOf(repoId));
                List<String> collect1 = schemas.stream().map(t -> Convert.toStr(t.get("schemaname"))).toList();
                return FXCollections.observableArrayList(collect1);
            }
        };
    }

    private Task<ObservableList<String>> getObservableTableListTask(Long repoId, String schemaName) {
        return new Task<>() {
            @Override
            protected ObservableList<String> call() throws Exception {
                List<LinkedHashMap<String, Object>> tableList = repositorySourceService.getTables(String.valueOf(repoId), schemaName);
                List<String> collect1 = tableList.stream().map(t -> Convert.toStr(t.get("tablename"))).toList();
                return FXCollections.observableArrayList(collect1);
            }
        };
    }

    private Task<ObservableList<String>> getObservableColumnListTask(Long repoId, String schemaName, String
            tableName, Boolean isReader) {
        return new Task<>() {
            @Override
            protected ObservableList<String> call() throws Exception {
                List<LinkedHashMap<String, Object>> columnList = repositorySourceService.getColumns(String.valueOf(repoId), schemaName, tableName);
                List<String> collect1 = columnList.stream().map(t -> Convert.toStr(t.get("columnname"))).toList();
                if (isReader) {
                    List<LinkedHashMap<String, Object>> primaryKeyList = columnList.stream().filter(t -> t.get("primarykey") != null
                            && Convert.toInt(t.get("primarykey"), 0).equals(1)
                            && StrUtil.containsAnyIgnoreCase((String) t.get("datatype"), "NUMBER", "INT")).toList();
                    if (!primaryKeyList.isEmpty()) {
                        splitPkTextField.setText((String) primaryKeyList.get(0).get("columnname"));
                    }
                }
                return FXCollections.observableArrayList(collect1);
            }
        };
    }

    @FXML
    public void handleSave() {
        updateModelFromUi();
        dataxJobService.save(dataxJob);
        AlertUtil.showSuccessAlert("保存成功");
        publishCustomEvent(String.valueOf(dataxGroupComboBox.getValue().getValue()));
    }

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    public void publishCustomEvent(final String message) {
        CustomSpringEvent customSpringEvent = new CustomSpringEvent(this, message);
        applicationEventPublisher.publishEvent(customSpringEvent);
    }

    public static Map<String, String> matchAndRemove(List<String> arr1, List<String> arr2) {
        Map<String, String> result = new HashMap<>();
        for (String item1 : arr1) {
            String left = item1;
            if (item1.indexOf('.') > -1) {
                left = item1.substring(item1.indexOf('.') + 1);
            }

            for (String item2 : arr2) {
                String right = item2;
                if (item2.indexOf('.') > -1) {
                    right = item2.substring(item2.indexOf('.') + 1);
                }

                if (left.equalsIgnoreCase(right)) {
                    result.put(item1, item2);
                    break;
                }
            }
        }
        return result;
    }


}
